package BackgammonBot.GameLogic;

import java.util.ArrayList;
import java.util.Stack;

import BackgammonBot.Interfaces.PlayerBase;


/**
 * This class is about all the logic, rules for the game.
 *
 * @author Dennis Meyer, Sebastian Brodehl
 * @version 0.0.1
 *
 */
public class BackgammonLogic 
{
    private byte[] backGammonBoard = new byte[26]; // w = negativ; b = positiv
    private byte whiteCheckersInHome = 0;
    private byte whiteCheckersAtStart = 0;
    private byte blackCheckersInHome = 0;
    private byte blackCheckersAtStart = 0;
    private int[] initialDice;
    private char initialPlayer;
    
    private Stack<int[]> history = new Stack<int[]>(); // 0 = from ; 1 = to; 2 = wCheckersInHome; 3 = WhiteCheckersAtStart
    // ; 4 = BlackCheckersAtHome, 5 = BlackCheckersAtStart; 6 = currentPlayer (-1 = w and 1 = b)
    
    // Getter and Setter
    
    public byte getWhiteCheckersInHome()
    {
    	return this.whiteCheckersInHome;
    }
    
    public byte getWhiteCheckersAtStart()
    {
    	return this.whiteCheckersAtStart;
    }
    
    public byte getBlackCheckersInHome()
    {
    	return this.blackCheckersInHome;
    }
    
    public byte getBlackCheckersAtStart()
    {
    	return this.blackCheckersAtStart;
    }
    
    public void setWhiteCheckersInHome(byte checkers)
    {
    	if (checkers > 0)
    	{
    		this.whiteCheckersInHome = checkers;
    	}
    }
    
    public void setWhiteCheckersAtStart(byte checkers)
    {
    	if (checkers > 0)
    	{
    		this.whiteCheckersAtStart = checkers;
    	}
    }
    
    public void setBlackCheckersInHome(byte checkers)
    {
    	if (checkers > 0)
    	{
    		this.blackCheckersInHome = checkers;
    	}
    }
    
    public void setBlackCheckersAtStart(byte checkers)
    {
    	if (checkers > 0)
    	{
    		this.blackCheckersAtStart = checkers;
    	}
    }
    
    public void setIntitialDice(int[] initialDice)
    {
    	this.initialDice = initialDice;
    }
    
    public int[] getInitialDice()
    {
    	return this.initialDice;
    }
    
    public void setInitialPlayer(char initialPlayer)
    {
    	this.initialPlayer = initialPlayer;
    }
    
    public char getInitialPlayer()
    {
    	return this.initialPlayer;
    }
    
    public byte[] getBoard()
    {
    	return this.backGammonBoard;
    }
    
    public void setBoard(byte[] board)
    {
    	this.backGammonBoard = board;
    }
    
    // Constructor
    public BackgammonLogic(byte[] board
    		, byte whiteCheckersInHome
    		, byte whiteCheckersAtStart
    		, byte blackCheckersInHome
    		, byte blackCheckersAtStart
    		, char initialPlayer
    		, int[] initialDice)
    {
    	this.backGammonBoard = board;
    	this.setWhiteCheckersInHome(whiteCheckersInHome);
    	this.setWhiteCheckersAtStart(whiteCheckersAtStart);
    	this.setBlackCheckersAtStart(blackCheckersAtStart);
    	this.setBlackCheckersInHome(blackCheckersInHome);
    	this.setInitialPlayer(initialPlayer);
    	this.setIntitialDice(initialDice);
    	
    	// Reads the initBoard from File and set it into the variables above.
        // IOGameFiles.readGameFile();
    }
    
    public BackgammonLogic() { }
    
    
    /**
     * Make a move at current Board and returns the fittness after that move. The board will not changed.
     * @param move The move, [0] = From, [1] = To
     * @param currentPlayer This stands for white (w) oder black (b)
     * @param player 
     * @return
     */
    public int setMoveAndGetFittness(int[] move, char currentPlayer, PlayerBase player)
    {
    	doMove(move, currentPlayer);
    	return player.calcFittnes(backGammonBoard, whiteCheckersInHome
    			, whiteCheckersAtStart, blackCheckersInHome, blackCheckersAtStart, currentPlayer);
    }
    
    public int calcFittness(char currentPlayer, PlayerBase player)
    {
    	return player.calcFittnes(backGammonBoard, whiteCheckersInHome
    			, whiteCheckersAtStart, blackCheckersInHome, blackCheckersAtStart, currentPlayer);
    }

    /**
     * Returns all possible hops for current player and only one dice.
     * @param board The board
     * @param dice A Number from 1 to 6
     * @param currentPlayer This stands for white (w) oder black (b)
     * @return A List with all possible Hops
     */
    public ArrayList<int[]> getPossibleHops(int dice, char currentPlayer)
    {
        ArrayList<int[]> possibleHops = new ArrayList<int[]>();

        for (int i = 0; i < 26; i++)
        {
            byte currentPoint = this.backGammonBoard[i];

            
            if ((currentPlayer == 'b' ? currentPoint > 0 : currentPoint < 0) 
            		|| (currentPlayer == 'b' ? i == 25 : i == 0))
            {
                // Create move: current possition plus dice value
                int[] move = new int[2]; // 0 = from , 1 = to
                move[0] = i;
                move[1] = currentPlayer == 'w' ? dice + i : i - dice;
                

                if (isMoveValid(this.backGammonBoard, move, currentPlayer))
                {
                	// Man muss nicht direkt ins Home laufen
                	if (move[1] < 0)
                		move[1] = 0;
                	else if (move[1] > 25)
                		move[1] = 25;
                	
                    possibleHops.add(move);
                }
            }
        }

        return possibleHops;
        
    }

    /**
     * Checks if a move ist valid
     * @param board The board, befor the move was taken
     * @param move The move.  [0] = From, [1] = To
     * @param currentPlayer And the player which makes the turn
     * @return
     */
    private Boolean isMoveValid(byte[] board, int[] move, char currentPlayer)
    {
    	/**
    	 * Arten von g��ltigen Z��gen
    	 * 
    	 * Player white
    	 * 
    	 * from muss zwischen 0 (inclu) und 24 (inclu ) liegen **CHECK
    	 * to muss zwischen 1 (inclu) und 25 (inclu) liegen **CHECK
    	 * 
    	 * der wert der in from steht muss < 0 sein **CHECK
    	 * der wert der in to steht muss == 0 oder == 1 sein **CHECK
    	 * 
    	 * to darf erst 25 sein, wenn alle eigenen checker auf den pl��tzen 19 (inclu) bis 25 (inclu) sind **CHECK
    	 * ein checker darf erst dann nicht direkt in das home gesetzt werden, wenn es keinen anderen checker gibt der mehr gesetzt werden kann. **CHECK
    	 * 
    	 * Player black
    	 * 
    	 * from muss zwischen 1 (inclu) und 25 (inclu) liegen **CHECK
    	 * to muss zwischen 0 (inclu) und 24 (inclu) liegen **CHECK
    	 * 
    	 * der wert der in from steht muss > 0 sein **CHECK
    	 * der wert der in to steht muss == 0 oder == -1 sein **CHECK
    	 * 
    	 * to darf erst 0 sein, wenn alle eigenen checker auf den pl��tzen 6 (inclu) bis 0 (inclu) sind **CHECK
    	 * ein checker darf erst dann nicht direkt in das home gesetzt werden, wenn es keinen anderen checker gibt der mehr gesetzt werden kann. **CHECK
    	 * 
    	 * 
    	 * Rangfolge beim ziehen. Wichtigkeit von oben abnehmend
    	 * 
    	 * 1. Ist ein Checker am Start muss dieser erst eingesetzt werden. Kann der Spieler dies nicht kann er keinen Zug machen **CHECK
    	 * 2. Ein Checker darf erst dann ins Home geschoben werden wenn alle eigenen Checker auf dem Home-Quadrant sind **CHECK
    	 * 3. Ein Checker darf erst dann nicht direkt ins Home geschoben werden, wenn es keinen anderen Checker gibt der bewegt werden kann **CHECK
    	 * 4. Ein Checker darf gem���� der normalen Regeln bewegt werden. **CHECK
    	 */
    	
    	
        int from = move[0];
        int to = move[1];

        if (currentPlayer == 'w' && to < from)
            return false;
        else if (currentPlayer == 'b' && from < to)
            return false;

        if (currentPlayer == 'w') // Valid moves for player white
        {
        	if (from < 0 || to > 30) // It is valid that to ist bigger than 25 because it is allowed
        		return false;
        	
        	// Is a white checker at start, this checker have to set into the board. This means that all moves where from is not 0 must be invalid 
        	if (this.whiteCheckersAtStart > 0 && from != 0)
        	{
        		return false;
        	}
        	
        	// All white checkers must be in field 19 - 24 for jumping into the home
        	if (to >= 25)
        	{
        		if (this.whiteCheckersAtStart > 0)
        		{
        			return false;
        		}
        		
        		boolean checkerIsOutsideHomeBoard = false;
        		for (int i = 1; i < 19; i++) 
        		{
        			checkerIsOutsideHomeBoard = this.backGammonBoard[i] < 0;
        			if (checkerIsOutsideHomeBoard)
        				return false;
				}
        		
        		if (to > 25)
        		{
	        		// Check if a checker exists, which can directly move into home or exits a checker which can move at board
	        		boolean ohterCheckerCanMove = false;
	        		for (int i = 19; i < from; i++) 
	        		{
	        			ohterCheckerCanMove = this.backGammonBoard[i] < 0; 
	        			if (ohterCheckerCanMove)
	        				return false;
					}
        		}
        		
        		if (!checkerIsOutsideHomeBoard)
        			return true;
        	}
        	
        	// Set checker from start to board
        	if (from == 0 && this.whiteCheckersAtStart > 0 && (board[to] == 0 || board[to] == 1 || board[to] < 0))
        		return true;
        	
            // Move is allowed if the field "from" contains a white checker and the field 
            // "to" is empty or contains one black checker or contains white checkers
            return board[from] < 0 && (board[to] == 0 || board[to] == 1 || board[to] < 0);
        }
        else // Valid moves for player black
        {
        	if (from > 25 || to < -5)
        		return false;
        
        	// Is a black checker at start, this checker have to set into the board. This means that all moves where from is not 25 must be invalid 
        	if (this.blackCheckersAtStart > 0 && from != 25)
        	{
        		return false;
        	}
        	
        	// All white black must be in field 1 - 6 for jumping into the home
        	if (to <= 0)
        	{
        		if (this.blackCheckersAtStart > 0)
        		{
        			return false;
        		}
        		
        		boolean checkerIsOutsideHomeBoard = false;
        		for (int i = 7; i < 25; i++) 
        		{
        			checkerIsOutsideHomeBoard = this.backGammonBoard[i] > 0;
        			if (checkerIsOutsideHomeBoard)
        				return false;
				}
        		
        		if (to < 0)
        		{
	        		// Check if a checker exists, which can directly move into home or exits a checker which can move at board
	        		boolean ohterCheckerCanMove = false;
	        		for (int i = 6; i > from; i--) 
	        		{
	        			ohterCheckerCanMove = this.backGammonBoard[i] > 0; 
	        			if (ohterCheckerCanMove)
	        				return false;
					}
        		}
        		
        		if (!checkerIsOutsideHomeBoard)
        			return true;
        	}
        	        	
        	if (from == 25 && this.blackCheckersAtStart > 0 && (board[to] == 0 || board[to] == -1 || board[to] > 0))
        		return true;
        	
            return board[from] > 0 && (board[to] == 0 || board[to] == -1 || board[to] > 0);
        }
    }
    
    public void undoMove()
    {
    	int[] historyMove = this.history.pop();
    	    	
    	// 0 = from ; 1 = to; 2 = wCheckersInHome; 3 = WhiteCheckersAtStart; 4 = BlackCheckersAtHome, 5 = BlackCheckersAtStart
    	
    	int from = historyMove[0];
		int to = historyMove[1];
		
		int wCheckersHomeBeforMove = historyMove[2];
		int wCheckersStartBeforMove = historyMove[3];
		int bCheckersHomeBeforMove = historyMove[4];
		int bCheckersStartBeforMove = historyMove[5];
		char currentPlayer = historyMove[6] == 1 ? 'b' : 'w';
    	
    	if (currentPlayer == 'w')
    	{
    		// If checker move from a field into home
    		if (wCheckersHomeBeforMove < this.whiteCheckersInHome)
    		{
    			this.whiteCheckersInHome--;
    			this.backGammonBoard[from]--;
    		}
    		// If checker move from start into the board
    		else if (wCheckersStartBeforMove > this.whiteCheckersAtStart)
    		{
    		    // Jump from start and eat black
    			if (bCheckersStartBeforMove < this.blackCheckersAtStart)
    			{
    				this.blackCheckersAtStart--;
    				this.backGammonBoard[to] = 1;
    			}
    			else
    			{
	    			this.backGammonBoard[to]++;
    			}
    			this.whiteCheckersAtStart++;
    		}
    		// If white checker eats a black checker
    		else if (bCheckersStartBeforMove < this.blackCheckersAtStart)
    		{
    			this.blackCheckersAtStart--;
    			this.backGammonBoard[to] = 1;
    			this.backGammonBoard[from]--;
    		}
    		// If checker only move
    		else 
    		{
    			this.backGammonBoard[to]++;
    			this.backGammonBoard[from]--;
    		}
    	}
    	else
    	{
    		// If checker move from a field into home
    		if (bCheckersHomeBeforMove < this.blackCheckersInHome)
    		{
    			this.blackCheckersInHome--;
    			this.backGammonBoard[from]++;
    		}
    		// If checker move from start into the board
    		else if (bCheckersStartBeforMove > this.blackCheckersAtStart)
    		{
    			// Jump from start and eat white
    			if (wCheckersStartBeforMove < this.whiteCheckersAtStart)
    			{
    				this.whiteCheckersAtStart--;
    				this.backGammonBoard[to] = -1;
    			}
    			else
    			{
	    			this.backGammonBoard[to]--;
    			}
    			this.blackCheckersAtStart++;
    		}
    		// If black checker eats a white checker
    		else if (wCheckersStartBeforMove < this.whiteCheckersAtStart)
    		{
    			this.whiteCheckersAtStart--;
    			this.backGammonBoard[to] = -1;
    			this.backGammonBoard[from]++;
    		}
    		// If checker only move
    		else 
    		{
    			this.backGammonBoard[to]--;
    			this.backGammonBoard[from]++;
    		}
    	}
    }

    public boolean doMove(int[] move, char currentPlayer)
    {
    	if (!isMoveValid(this.backGammonBoard, move, currentPlayer))
    		return false;
    	
    	// 0 = from ; 1 = to; 2 = wCheckersInHome; 3 = WhiteCheckersAtStart; 4 = BlackCheckersAtHome, 5 = BlackCheckersAtStart
    	int[] historyMove = new int [7];
    	historyMove[0] = move[0];
    	historyMove[1] = move[1];
    	historyMove[2] = this.whiteCheckersInHome;
    	historyMove[3] = this.whiteCheckersAtStart;
    	historyMove[4] = this.blackCheckersInHome;
    	historyMove[5] = this.blackCheckersAtStart;
    	historyMove[6] = currentPlayer == 'w' ? -1 : 1;
    	
    	this.history.add(historyMove);
    	
    	if (currentPlayer == 'b') // Blacks Turn
        {
    		 if (move[0] == 25)
    		 {
    			 // Jump from start into board
    			 this.blackCheckersAtStart--;
    			 
    			 // Jump from start and eat white checker
    			 if (this.backGammonBoard[move[1]] < 0)
    			 {
    				 this.backGammonBoard[move[1]] = 1;
                	 this.whiteCheckersAtStart++;     
    			 }
    			 else
    			 {
    				 this.backGammonBoard[move[1]]++;
    			 }
    		 }
    		 else if (move[1] != 0)
             {
    			 this.backGammonBoard[move[0]]--;
    			 
                 // Eat white checker
                 if (this.backGammonBoard[move[1]] < 0)
                 {
                	 this.backGammonBoard[move[1]] = 1;
                	 this.whiteCheckersAtStart++;         
                 }
                 // Do a normal turn
                 else
                 {
                	 this.backGammonBoard[move[1]]++;
                 }
             }
             else
             {
            	 this.backGammonBoard[move[0]]--;
            	 
            	 // Jump in home
            	 this.blackCheckersInHome++;          
             }
         }
         else // White's turn
         {
        	 if (move[0] == 0)
    		 {
    			 // Jump from start into board
    			 this.whiteCheckersAtStart--;
    			 
    			 // Jump from start and eat white checker
    			 if (this.backGammonBoard[move[1]] > 0)
    			 {
    				 this.backGammonBoard[move[1]] = -1;
                	 this.blackCheckersAtStart++;     
    			 }
    			 else
    			 {
    				 this.backGammonBoard[move[1]]--;
    			 }
    		 }
        	 else if (move[1] != 25)
             {
        		 this.backGammonBoard[move[0]]++;
        		 
                 // Eat black checker
                 if (this.backGammonBoard[move[1]] > 0)
                 {
                	 this.backGammonBoard[move[1]] = -1;
                	 this.blackCheckersAtStart++;
                 }
                 // Do a normal turn
                 else
                 {
                     this.backGammonBoard[move[1]]--;
                 }
             }
             else
             {
            	 this.backGammonBoard[move[0]]++;
            	 
                 // Jump in home
            	 this.whiteCheckersInHome++;
             }
         }
    	
    	return true;
    }

	public String printBoard() 
	{
		StringBuilder sb = new StringBuilder();
		sb.append(0);
		sb.append(':');
		sb.append('w');
		sb.append(this.whiteCheckersAtStart);
		sb.append(',');
		sb.append('b');
		sb.append(this.blackCheckersInHome);
		sb.append(System.getProperty("line.separator"));
		
		
		for (int i = 1; i < 25; i++) 
		{
			byte b = 0;
			int w = 0;
			if(this.backGammonBoard[i] < 0)
				w = this.backGammonBoard[i] * -1 ;
			else
				b = this.backGammonBoard[i];
			sb.append(i);
			sb.append(':');
			sb.append('w');
			sb.append(w);
			sb.append(',');
			sb.append('b');
			sb.append(b);
			sb.append(System.getProperty("line.separator"));
		}
		
		sb.append(25);
		sb.append(':');
		sb.append('w');
		sb.append(this.whiteCheckersInHome);
		sb.append(',');
		sb.append('b');
		sb.append(this.blackCheckersAtStart);
		sb.append(System.getProperty("line.separator"));
		
		
		return sb.toString();
	}

	public void clearBoard()
	{
		for (int i = 0; i < this.backGammonBoard.length; i++) 
		{
			this.backGammonBoard[i] = 0;
		}
	}

}